/**
 * @file oled_ssd1306.c
 * @brief SLE Client implementation for device discovery and connection management.
 *
 * This file contains the implementation of the SLE client, which is responsible for
 * discovering devices, managing connections, and handling communication with remote devices.
 *
 * @author Maker Studio
 * @license Apache 2.0
 *
 * This code is open-sourced under the Apache 2.0 license. When using open-source libraries
 * or code, please ensure to acknowledge the original authors and sources.
 */


#include <stdio.h>      // Standard input/output
#include <stddef.h>     // Standard type definitions

#include "iot_gpio.h"  // OpenHarmony HAL: IoT hardware device operation interface - GPIO
#include "../inc/iot_gpio_ex.h"
#include "iot_i2c.h"   // OpenHarmony HAL: IoT hardware device operation interface - I2C
#include "iot_errno.h" // OpenHarmony HAL: IoT hardware device operation interface - error codes definition
#include "../inc/iot_gpio_ex.h"
// Font library header file
#include "../inc/oled_fonts.h"

// OLED display simplified driver interface file
#include "../inc/oled_ssd1306.h"

// Macro to calculate the size of an array
#define ARRAY_SIZE(a) sizeof(a) / sizeof(a[0])

// Define a macro to identify I2C1
#define OLED_I2C_IDX 1

// Define a macro for I2C1 baud rate (transfer rate)
#define OLED_I2C_BAUDRATE (400 * 1000) // 400KHz

// Define a macro for the OLED width
#define OLED_WIDTH (128)

// Define a macro for the SSD1306 display driver chip's device address
#define OLED_I2C_ADDR 0x3C // 0x78

// Define a macro for write command operation
#define OLED_I2C_CMD 0x00  // 0000 0000 Write command

// Define a macro for write data operation
#define OLED_I2C_DATA 0x40 // 0100 0000 (0x40) Write data

// Define a macro for 100ms delay
#define DELAY_100_MS (100 * 1000)

// Define a structure for sending or receiving data
typedef struct
{
    // Pointer to data to send
    unsigned char *sendBuf;
    // Length of data to send
    unsigned int sendLen;
    // Pointer to data to receive
    unsigned char *receiveBuf;
    // Length of data to receive
    unsigned int receiveLen;
} IotI2cData;

/// @brief  Write a byte to OLED
/// @param  regAddr Write command or data (OLED_I2C_CMD / OLED_I2C_DATA)
/// @param  byte The content to write
/// @retval Returns IOT_SUCCESS on success, IOT_FAILURE on failure
static uint32_t I2cWriteByte(uint8_t regAddr, uint8_t byte)
{
    // Define the byte stream
    uint8_t buffer[] = {regAddr, byte};
    IotI2cData i2cData = {0};
    i2cData.sendBuf = buffer;
    i2cData.sendLen = sizeof(buffer) / sizeof(buffer[0]);

    // Send the byte stream
    return IoTI2cWrite(OLED_I2C_IDX, OLED_I2C_ADDR, i2cData.sendBuf, i2cData.sendLen);
}

/// @brief Write a command byte to OLED
/// @param cmd The command byte to write
/// @return Returns IOT_SUCCESS on success, IOT_FAILURE on failure
static uint32_t WriteCmd(uint8_t cmd)
{
    return I2cWriteByte(OLED_I2C_CMD, cmd);
}

/// @brief Write a data byte to OLED
/// @param cmd The data byte to write
/// @return Returns IOT_SUCCESS on success, IOT_FAILURE on failure
uint32_t WriteData(uint8_t data)
{
    return I2cWriteByte(OLED_I2C_DATA, data);
}

/// @brief Initialize the SSD1306 display driver chip
uint32_t OledInit(void)
{
    // Initialization commands
    static const uint8_t initCmds[] = {
        0xAE, // Display off
        0x00, // Set lower 4 bits of column address to 0000 for page addressing mode
        0x10, // Set upper 4 bits of column address to 0000 for page addressing mode
        0x40, // Set start line address to line 0
        0xB0, // Set page start address to PAGE0 for page addressing mode
        0x81, // Set contrast control
        0xFF, // Maximum contrast
        0xA1, // Set segment remap
        0xA6, // Normal display (0 for pixel off, 1 for pixel on)
        0xA8, // Set multiplex ratio (1 to 64)
        0x3F, // 1/32 duty
        0xC8, // COM scan direction
        0xD3, // Set display offset
        0x00, //
        0xD5, // Set oscillator frequency
        0x80, //
        0xD8, // Set color mode off for area
        0x05, //
        0xD9, // Set pre-charge period
        0xF1, //
        0xDA, // Set COM pin configuration
        0x12, //
        0xDB, // Set Vcomh
        0x30, //
        0x8D, // Enable charge pump
        0x14, //
        0xAF, // Display on
    };

    // Initialize GPIO-15
    IoTGpioInit(IOT_IO_NAME_GPIO_15);
    // Set GPIO-15 pin function to I2C1_SDA
    IoSetFunc(IOT_IO_NAME_GPIO_15, IOT_IO_FUNC_GPIO_15_I2C1_SDA);
    // Initialize GPIO-16
    IoTGpioInit(IOT_IO_NAME_GPIO_16);
    // Set GPIO-16 pin function to I2C1_SCL
    IoSetFunc(IOT_IO_NAME_GPIO_16, IOT_IO_FUNC_GPIO_16_I2C1_SCL);

    // Initialize I2C1 with specified baud rate
    IoTI2cInit(OLED_I2C_IDX, OLED_I2C_BAUDRATE);

    // Send initialization commands to initialize the SSD1306 display driver chip
    for (size_t i = 0; i < ARRAY_SIZE(initCmds); i++)
    {
        // Send a command byte
        uint32_t status = WriteCmd(initCmds[i]);
        if (status != IOT_SUCCESS)
        {
            return status;
        }
    }

    // OLED initialization complete, return success
    return IOT_SUCCESS;
}

/// @brief Set the display position
/// @param x x-coordinate, in pixels
/// @param y y-coordinate, in multiples of 8 pixels (page start address)
/// @return None
void OledSetPosition(uint8_t x, uint8_t y)
{
    // Set page address
    WriteCmd(0xb0 + y);

    // Column address: 0 to 127
    // For column 0: 0x00 (low byte 00000000), high byte 0000 + 0x10 = 0x10
    // For column 127: 0x7F (low byte 11111111), high byte 0111 + 0x10 = 0x17

    // Set column address: lower 4 bits
    WriteCmd(x & 0x0f);

    // Set column address: upper 4 bits
    WriteCmd(((x & 0xf0) >> 4) | 0x10);
}

/// @brief Full-screen fill
/// @param fillData Data to fill the screen with (1 byte)
/// @return None
void OledFillScreen(uint8_t fillData)
{
    // Variables for traversing pages and columns
    uint8_t m = 0;
    uint8_t n = 0;

    // Write data to all pages
    for (m = 0; m < 8; m++)
    {
        // Set page address: 0 to 7
        WriteCmd(0xb0 + m);

        // Set column address to 0
        WriteCmd(0x00); // Set column address low byte
        WriteCmd(0x10); // Set column address high byte

        // Write 128 columns of data
        for (n = 0; n < 128; n++)
        {
            // Write one byte of data
            WriteData(fillData);
        }
    }
}

/// @brief Display a character
/// @param x: x-coordinate, in pixels
/// @param y: y-coordinate, in multiples of 8 pixels (page start address)
/// @param ch: Character to display
/// @param font: Font type
void OledShowChar(uint8_t x, uint8_t y, uint8_t ch, Font font)
{
    // Array index
    uint8_t c = 0;

    // Loop control
    uint8_t i = 0;

    // Get the array index
    // ASCII code for space is 32, its index in the font is 0
    c = ch - ' ';

    // Display character
    if (font == FONT8x16) // 8x16 matrix, requires two pages
    {
        // Display upper half of the character
        OledSetPosition(x, y);
        for (i = 0; i < 8; i++)
        {
            WriteData(F8X16[c * 16 + i]);
        }

        // Display lower half of the character
        OledSetPosition(x, y + 1);
        for (i = 0; i < 8; i++)

        {
            WriteData(F6x8[c][i]);
        }
    }
}

/// @brief Displays a string
/// @param x: x-coordinate, in pixels (1 pixel unit)
/// @param y: y-coordinate, in pixels (8 pixels unit)
/// @param str: The string to be displayed
/// @param font: Font type
void OledShowString(uint8_t x, uint8_t y, const char *str, Font font)
{
    // Character array (string) index
    uint8_t j = 0;

    // Check if the string is NULL
    if (str == NULL)
    {
        printf("param is NULL, Please check!!!\r\n");
        return;
    }

    // Iterate through the string and display each character
    while (str[j])
    {
        // Display one character
        OledShowChar(x, y, str[j], font);

        // Set the character spacing
        x += 8;

        // If the next character exceeds the OLED display range, move to the next line
        if (x > 120)
        {
            x = 0;
            y += 2;
        }

        // Move to the next character
        j++;
    }
}
